
# include "dfxRenderData_OGL.h"
# include "dfxGraphicsDevice_OGL.h"
# include "dfxLog.h"

namespace DFX
{
    RenderData_OGL::RenderData_OGL(GraphicsDevice *gd, const String &name)
        : RenderData(gd, name), mVBHandle(0), mIBHandle(0)
    {
        glGenVertexArrays(1, &mVAOHandle);

        if (!mVAOHandle)
            DFX_ERROR( "Unable to generate vertex array: %s", mName.c_str());
    }

    RenderData_OGL::~RenderData_OGL()
    {
        Destroy();
    }

    DFXRESULT RenderData_OGL::CreateVertexBuffer(BUFFER_USAGE vbUsage, VertexFormat *format, UInt32 numVertices, void *initialData)
    {
        if (!mVAOHandle)
        {
            DFX_ERROR( "Vertex array is not generated: %s", mName.c_str());
            return DFX_CREATEFAILED;
        }

        if (mVBHandle)
            glDeleteBuffers(1, &mVBHandle);

        glBindVertexArray(mVAOHandle);
        glGenBuffers(1, &mVBHandle);

        if (!mVBHandle)
        {
            DFX_ERROR( "Unable to generate vertex buffer : %s", mName.c_str());
            return DFX_CREATEFAILED;
        }

        glBindBuffer(GL_ARRAY_BUFFER, mVBHandle);

        for (UInt32 i = 0; i < format->GetNumElements(); i++)
        {
            VertexElement *ele = format->GetElement(i);

            glVertexAttribPointer(ele->GetIndex(), ele->GetSize() / 4, GraphicsDevice_OGL::VertexElementFormat[ele->GetFormat()], 
                GL_FALSE, format->GetVertexSize(), (const GLvoid*)ele->GetOffset());

            glEnableVertexAttribArray(ele->GetIndex());
        }
        
        glBufferData(GL_ARRAY_BUFFER, format->GetVertexSize() * numVertices,
            initialData, GraphicsDevice_OGL::BufferUsage[vbUsage]);                   

        mNumVertices = numVertices;
        mVBUsage = vbUsage;

        glBindVertexArray(0);

        return DFX_SUCCESS;
    }

    DFXRESULT RenderData_OGL::CreateIndexBuffer(BUFFER_USAGE ibUsage, UInt32 indexSize, UInt32 numIndices, void *initialData)
    {
        if (!mVAOHandle)
        {
            DFX_ERROR( "Vertex array is not generated: %s", mName.c_str());
            return DFX_CREATEFAILED;
        }

        if (mIBHandle)
            glDeleteBuffers(1, &mIBHandle);

        glBindVertexArray(mVAOHandle);
        glGenBuffers(1, &mIBHandle);

        if (!mIBHandle)
        {
            DFX_ERROR( "Unable to generate index buffer: %s", mName.c_str());
            return DFX_CREATEFAILED;
        }

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIBHandle);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexSize * numIndices,
                initialData, GraphicsDevice_OGL::BufferUsage[mIBUsage]);       

        mIBUsage = ibUsage;
        mNumIndices = numIndices;
        mIndexSize = indexSize;
        mIndexType = (indexSize == 2) ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT;

        glBindVertexArray(0);

        return DFX_SUCCESS;
    }

    bool RenderData_OGL::HasIndices()
    {
        return mNumIndices > 0;
    }

    DFXRESULT RenderData_OGL::MapVertexBuffer(void **buffer, UInt32 mapFlags)
    {
        GLuint flags = 0;

        if ((mapFlags & MAP_FLAGS_READ) && (mapFlags & MAP_FLAGS_WRITE))
            flags |= GL_READ_WRITE;
        else {
            if (mapFlags & MAP_FLAGS_READ)
                flags |= GL_READ_ONLY;

            if (mapFlags & MAP_FLAGS_WRITE)
                flags |= GL_WRITE_ONLY;
        }

        glBindBuffer(GL_ARRAY_BUFFER, mVBHandle);
        
        *buffer = NULL;
        *buffer = glMapBuffer(GL_ARRAY_BUFFER, flags);

        if (glGetError() != GL_NO_ERROR || !*buffer)
        {
            DFX_ERROR( "Unable to map buffer range");
            return DFX_ERROR0;
        }

        return DFX_SUCCESS;
    }

    DFXRESULT RenderData_OGL::UnmapVertexBuffer()
    {
        glBindBuffer(GL_ARRAY_BUFFER, mVBHandle);
        glUnmapBuffer(GL_ARRAY_BUFFER);

        if (glGetError() != GL_NO_ERROR)
        {
            DFX_ERROR( "Unable to unmap buffer range");
            return DFX_ERROR0;
        }

        return DFX_SUCCESS;
    }

    DFXRESULT RenderData_OGL::MapIndexBuffer(void **buffer, UInt32 mapFlags)
    {
        GLuint flags = 0;

        if ((mapFlags & MAP_FLAGS_READ) && (mapFlags & MAP_FLAGS_WRITE))
            flags |= GL_READ_WRITE;
        else {
            if (mapFlags & MAP_FLAGS_READ)
                flags |= GL_READ_ONLY;

            if (mapFlags & MAP_FLAGS_WRITE)
                flags |= GL_WRITE_ONLY;
        }

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIBHandle);

        *buffer = NULL;
        *buffer = glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, flags);

        if (glGetError() != GL_NO_ERROR || !*buffer)
        {
            DFX_ERROR( "Unable to map buffer range");
            return DFX_ERROR0;
        }

        return DFX_SUCCESS;
    }

    DFXRESULT RenderData_OGL::UnmapIndexBuffer()
    {
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIBHandle);
        glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER);

        if (glGetError() != GL_NO_ERROR)
        {
            DFX_ERROR( "Unable to unmap buffer range");
            return DFX_ERROR0;
        }

        return DFX_SUCCESS;
    }

    void RenderData_OGL::Activate()
    {
        if (mVAOHandle)
            glBindVertexArray(mVAOHandle);
    }

    void RenderData_OGL::Draw(PRIMITIVE_TYPE type)
    {
        if (mIBHandle)
        {
            switch (type)
            {
            case PRIMITIVE_TYPE_POINT_LIST:
                {
                    glDrawArrays(GL_POINTS, 0, mNumVertices);
                }
                break;

            case PRIMITIVE_TYPE_LINE_LIST:
                {
                    glDrawElements(GL_LINE, mNumIndices / 2, mIndexType, ((GLubyte *)NULL + (0)));
                }
                break;

            case PRIMITIVE_TYPE_LINE_STRIP:
                {
                    glDrawElements(GL_LINE_STRIP, mNumVertices, mIndexType, ((GLubyte *)NULL + (0)));
                }
                break;

            default:
                {
                    glDrawElements(GL_TRIANGLES, mNumIndices / 3, mIndexType, ((GLubyte *)NULL + (0)));
                }
                break;
            }
        }
        else
        {
            glDrawArrays(GraphicsDevice_OGL::PrimitiveType[type], 0, mNumVertices);
        }
    }

    void RenderData_OGL::Draw(PRIMITIVE_TYPE type, UInt32 start, UInt32 end, UInt32 count)
    {
        if (mIBHandle)
        {
            switch (type)
            {
            case PRIMITIVE_TYPE_POINT_LIST:
                {
                    glDrawArrays(GL_POINTS, start, mNumVertices);
                }
                break;

            case PRIMITIVE_TYPE_LINE_LIST:
                {
                    glDrawRangeElements(GL_LINE, start, end, count, mIndexType, ((GLubyte *)NULL + (0)));
                }
                break;

            case PRIMITIVE_TYPE_LINE_STRIP:
                {
                    glDrawRangeElements(GL_LINE_STRIP, start, end, count, mIndexType, ((GLubyte *)NULL + (0)));
                }
                break;

            default:
                {
                    glDrawRangeElements(GL_TRIANGLES, start, end, count, mIndexType, ((GLubyte *)NULL + (0)));
                }
                break;
            }
        }
        else
        {
            glDrawArrays(GraphicsDevice_OGL::PrimitiveType[type], start, count);
        }
    }

    void RenderData_OGL::Destroy()
    {
        if (mVBHandle)	glDeleteBuffers(1, &mVBHandle);
        if (mIBHandle)	glDeleteBuffers(1, &mIBHandle);

        if (mVAOHandle)
            glDeleteVertexArrays(1, &mVAOHandle);

        mVAOHandle = 0;
        mVBHandle = 0;
        mIBHandle = 0;
    }
}